/* 
 * File:   Connection.cpp
 * Author: Michal Lisicki
 * 
 * Created on January 17, 2012, 12:55 PM
 */

#include <stdio.h> 
#include <cstring> 
#include <stdlib.h>
#include <pthread.h>
#include <algorithm>

#include "PacketHandler/PacketSIP.h"
#include "Parser/SIPParser.h"
#include "ApplicationManager.h"

#include "Connection.h"

#define BUFSIZE 8192

const char* Connection::LOCALHOST = "127.0.0.1";
// const char* Connection::SIP_PROXY_HOST = "sip.tpad.com";
const char* Connection::SIP_PROXY_HOST = "213.40.29.11";

Connection::Connection() {
    sipApplicationPort_ = 0;
    sipExternalPort_ = SIP_EXTERNAL_PORT;
    sipProxyPort_ = SIP_PROXY_PORT;    
    sipInternalPort_ = SIP_INTERNAL_PORT;
    sipProxyHost_.assign(SIP_PROXY_HOST);
    rtpExternalPort_ = RTP_EXTERNAL_PORT;

    rtpApplicationPort_ = 0; 
    rtpProxyPort_ = 0;
    rtpProxyHost_ = "";
}

void* Connection::listenOnSockets( void *ptr ) {
//    Connection* classPtr = (Connection*) ptr;
//    int slen;
//    int pktSize;
//    Parser::SIPParser sipParser;
//    char* content;
//    std::string msg;
//    int error;
//    
//    struct addrinfo hints, *addrInfoResult;
//    memset (&hints, 0, sizeof (hints));
//    hints.ai_family = AF_INET;
//    
//    if (getaddrinfo(classPtr->sipProxyHost_.c_str(), NULL, NULL, &addrInfoResult) != 0) {   
//        fprintf(stderr, "getaddrinfo() failed\n");
//        exit(1);
//    }
//    
//    sockaddr_in siFrom;
//    slen = sizeof(siFrom);
//    char buf[BUFSIZE];
//    
//    fd_set ready;
//    FD_ZERO(&ready);
//    FD_SET(classPtr->SIPSock_,&ready);
//    FD_SET(classPtr->RTPSock_,&ready);
//    
//    struct timeval tv;
//    tv.tv_sec  = 100;
//    tv.tv_usec = 0;
//    
//    while (1) {
//        int retval = select(FD_SETSIZE,&ready,0,0,&tv);
//
//        if(retval < 0) printf("select error\n");
//
//        if(FD_ISSET(classPtr->SIPSock_,&ready)) {
//            pktSize = recvfrom(classPtr->SIPSock_, buf, BUFSIZE, 0, (struct sockaddr *)&siFrom,(socklen_t*) &slen);
//            buf[pktSize] = '\0';
//        //       printf("\nReceived packet from %s:%d\n",inet_ntoa(siFrom.sin_addr), ntohs(siFrom.sin_port));       
//            PacketHandler::PacketSIP pktSIP = sipParser.generatePacket(buf);
//            if(siFrom.sin_addr.s_addr == ((struct sockaddr_in *) addrInfoResult->ai_addr)->sin_addr.s_addr) {
//
//        //          printf("Received packet from %s:%d\nData: %s\n\n", inet_ntoa(siSIPListen_.sin_addr), ntohs(siSIPListen_.sin_port), buf);
//
//                // set the RTP port and IP if available
//                int rtpProxyPort;
//                std::string rtpProxyHost;
//                if((rtpProxyPort = pktSIP.getSDP()->getRTPPort())!=-1) {
//                    classPtr->rtpProxyPort_ =rtpProxyPort;
//                    pktSIP.getSDP()->setRTPPort(classPtr->rtpExternalPort_);
//                }
//                if((rtpProxyHost = pktSIP.getSDP()->getRTPIP())!="") {
//                    classPtr->rtpProxyHost_ = rtpProxyHost;
//                    pktSIP.getSDP()->setRTPIP(LOCALHOST);
//                }
//
//                // check if socket structure already set up
//                if(classPtr->sipApplicationPort_ != 0) {
//                    slen = sizeof(classPtr->siSIPApplication_);
//                    // prepare the message to send
//                    msg = pktSIP.getMsg();
//                    if (sendto(classPtr->SIPSock_, msg.c_str(), msg.size(),0, (struct sockaddr *) &(classPtr->siSIPApplication_),slen) == -1) {
//                        perror("sending sip to application failed");
//                        close(classPtr->SIPSock_);
//                    }
//                }
//            } else if(siFrom.sin_addr.s_addr == inet_addr(LOCALHOST)) {
//        //           printf("Received packet from %s:%d\nData: %s\n\n", inet_ntoa(siSIPListen_.sin_addr), ntohs(siSIPListen_.sin_port), buf);
//
//                // Xlite receives data on the same port it sends, so we can use that port
//                // for communicating back
//                if(classPtr->sipApplicationPort_ == 0) {
//                        classPtr->sipApplicationPort_ = ntohs(siFrom.sin_port);
//                        // Sending back to voip application
//                        classPtr->siSIPApplication_.sin_family = AF_INET;
//                        classPtr->siSIPApplication_.sin_port = htons(classPtr->sipApplicationPort_);
//                        if (inet_aton(LOCALHOST, (struct in_addr *)&(classPtr->siSIPApplication_.sin_addr))==0) {
//                            fprintf(stderr, "inet_aton() failed\n");
//                            exit(1);
//                        }
//                }
//                // set the RTP port if available
//                int rtpApplicationPort;
//                if((rtpApplicationPort = pktSIP.getSDP()->getRTPPort())!=-1) {
//                    classPtr->rtpApplicationPort_ = rtpApplicationPort;
//                    pktSIP.getSDP()->setRTPPort(classPtr->rtpExternalPort_);
//                }
//                // prepare the message to send
//                msg = pktSIP.getMsg();
//                slen = sizeof(classPtr->siSIPProxy_);    
//                if (sendto(classPtr->SIPSock_, msg.c_str(), msg.size(),0, (struct sockaddr *) &(classPtr->siSIPProxy_),slen) == -1) {
//                    perror("sending packet to sip proxy failed");
//                    close(classPtr->SIPSock_);
//                }
//            }
//            std::cout << msg << std::endl << std::endl;
//        } else if (FD_ISSET(classPtr->RTPSock_,&ready)) {
//            printf("RTP Packet received from %s:%d\nData: %s\n\n", inet_ntoa(classPtr->siRTPListen_.sin_addr), ntohs(classPtr->siRTPListen_.sin_port), buf);
//        }
//    }
}

bool Connection::init() {
#ifdef _WIN32
    WSADATA WsaData;
    WSAStartup(MAKEWORD(2,2), &WsaData);
#endif
    struct hostent *hp, *gethostbyname();
    pthread_t listeningThread;
    int slen;
    int pktSize;
    Parser::SIPParser sipParser;
    // Message for proxy (not from)
    std::string msgProxy = "";
    // Message for application
    std::string msgApplication = "";
    sockaddr_in siFrom;
    slen = sizeof(siFrom);
    char buf[BUFSIZE];
    
    SIPApplicationSock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (SIPApplicationSock_!=-1) {
            connected_ = true;
    }
    SIPProxySock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (SIPProxySock_!=-1) {
            connected_ = true;
    }
    
    struct addrinfo hints, *addrInfoResult;
    memset (&hints, 0, sizeof (hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_DGRAM;
    
    // converts human-readable text strings representing hostnames or IP 
    // addresses into a dynamically allocated linked list of struct addrinfo 
    // structure (instead of gethostname)
    if (getaddrinfo(sipProxyHost_.c_str(), NULL, NULL, &addrInfoResult) != 0) {   
        fprintf(stderr, "getaddrinfo() failed\n");
        exit(1);
    }

    std::cout << "Configuring SIP Communication... " << std::endl;    
    
    // Setting SIP proxy communication
    siSIPProxyListen_.sin_family = AF_INET;
    siSIPProxyListen_.sin_port = htons(sipExternalPort_);
    siSIPProxyListen_.sin_addr.s_addr = htonl(INADDR_ANY);
    
    if (bind(SIPProxySock_,(struct sockaddr *)&siSIPProxyListen_, sizeof siSIPProxyListen_) == -1) {
        perror("binding datagram socket");
        exit(1);
    }

    siSIPProxy_.sin_family = AF_INET;
    siSIPProxy_.sin_port = htons(sipProxyPort_);
//    siSIPProxy_.sin_addr = ((struct sockaddr_in *) addrInfoResult->ai_addr)->sin_addr;       
    if (inet_aton(SIP_PROXY_HOST, (struct in_addr *)&(siSIPProxy_.sin_addr))==0) {
        fprintf(stderr, "inet_aton() failed\n");
        exit(1);
    }
    
    // Setting SIP application communication
    siSIPApplicationListen_.sin_family = AF_INET;
    siSIPApplicationListen_.sin_port = htons(sipInternalPort_);
    siSIPApplicationListen_.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(SIPApplicationSock_,(struct sockaddr *)&siSIPApplicationListen_, sizeof siSIPApplicationListen_) == -1) {
        perror("binding datagram socket");
        exit(1);
    }
    
    // Waiting for application port
    while (sipApplicationPort_ == 0) {
        if((pktSize = recvfrom(SIPApplicationSock_, buf, BUFSIZE, 0, (struct sockaddr *)&siFrom,(socklen_t*) &slen)) == -1) {
            perror("error while receiving datagram");
            exit(1);
        }
        // Xlite receives data on the same port it sends, so we can use that port
        // for communicating back
        if(siFrom.sin_addr.s_addr == inet_addr(LOCALHOST)) {
            sipApplicationPort_ = ntohs(siFrom.sin_port);
            // Sending back to voip application
            siSIPApplication_.sin_family = AF_INET;
            siSIPApplication_.sin_port = htons(sipApplicationPort_);
            if (inet_aton(LOCALHOST, (struct in_addr *)&(siSIPApplication_.sin_addr))==0) {
                fprintf(stderr, "inet_aton() failed\n");
                exit(1);
            }
            buf[pktSize] = '\0';
            msgProxy.assign(buf);            
        }
    }
    std::cout << "Local SIP Port: " << sipApplicationPort_ << std::endl;
    

    fd_set readSet;
    fd_set writeSet;
    FD_ZERO(&readSet);
    FD_ZERO(&writeSet);
    FD_SET(SIPApplicationSock_,&readSet); 
    FD_SET(SIPApplicationSock_,&writeSet);    
    FD_SET(SIPProxySock_,&readSet);
    FD_SET(SIPProxySock_,&writeSet);
    
    int fdMax = std::max(SIPApplicationSock_, SIPProxySock_);
        
    struct timeval tv;
    tv.tv_sec  = 3600;
    tv.tv_usec = 0;
    
    // condition checks if new packet has been received and if we still have some missing information
    // to establish rtp connection
    while( (rtpApplicationPort_ == 0) || (rtpProxyPort_ == 0) || (rtpProxyHost_ == "") ) {
        int retval;
        if(msgApplication.empty()) {
            retval = select(fdMax+1,&readSet,0,0,NULL);
        } else {
            retval = select(fdMax+1,0,&writeSet,0,NULL);
        }
        if(retval < 0) printf("select error\n");
        
        if(retval>0) {
            if(FD_ISSET(SIPProxySock_,&readSet)) {
                if((pktSize = recvfrom(SIPProxySock_, buf, BUFSIZE, 0, (struct sockaddr *)&siFrom,(socklen_t*) &slen)) == -1) {
                    perror("error while receiving datagram");
                    exit(1);
                }
                buf[pktSize] = '\0';
                printf("\nReceived packet from %s:%d\n",inet_ntoa(siFrom.sin_addr), ntohs(siFrom.sin_port));
                PacketHandler::PacketSIP pktSIP = sipParser.generatePacket(buf);
                // set the RTP port and IP if available
    //            int rtpProxyPort;
    //            std::string rtpProxyHost;
    //            if((rtpProxyPort = pktSIP.getSDP()->getRTPPort())!=-1) {
    //                rtpProxyPort_ =rtpProxyPort;
    //                pktSIP.getSDP()->setRTPPort(rtpExternalPort_);
    //            }
    //            if((rtpProxyHost = pktSIP.getSDP()->getRTPIP())!="") {
    //                rtpProxyHost_ = rtpProxyHost;
    //                pktSIP.getSDP()->setRTPIP(LOCALHOST);
    //            }
                msgApplication = pktSIP.getMsg();                
            }
            if(FD_ISSET(SIPApplicationSock_,&writeSet)) {
                std::cout << msgApplication << std::endl << std::endl;
                // prepare the message to send
                slen = sizeof(siSIPApplication_);
                if (sendto(SIPApplicationSock_, msgApplication.c_str(), msgApplication.size(),0, (struct sockaddr *) &(siSIPApplication_),slen) == -1) {
                    perror("sending sip to application failed");
                    close(SIPApplicationSock_);
                }
                msgApplication.clear();

            }
            if(FD_ISSET(SIPApplicationSock_,&readSet)) {
                if((pktSize = recvfrom(SIPApplicationSock_, buf, BUFSIZE, 0, (struct sockaddr *)&siFrom,(socklen_t*) &slen)) == -1) {
                    perror("error while receiving datagram");
                    exit(1);
                }
                buf[pktSize] = '\0';
                printf("\nReceived packet from %s:%d\n",inet_ntoa(siFrom.sin_addr), ntohs(siFrom.sin_port));       
                PacketHandler::PacketSIP pktSIP = sipParser.generatePacket(buf);

    //            // set the RTP port if available
    //            int rtpApplicationPort;
    //            if((rtpApplicationPort = pktSIP.getSDP()->getRTPPort())!=-1) {
    //                rtpApplicationPort_ = rtpApplicationPort;
    //                pktSIP.getSDP()->setRTPPort(rtpExternalPort_);
    //            }
                msgProxy = pktSIP.getMsg();
            }
            if(FD_ISSET(SIPProxySock_,&writeSet)) {
                std::cout << msgProxy << std::endl << std::endl;
                // prepare the message to send
                slen = sizeof(siSIPProxy_);    
                if (sendto(SIPProxySock_, msgProxy.c_str(), msgProxy.size(),0, (struct sockaddr *) &(siSIPProxy_),slen) == -1) {
                    perror("sending packet to sip proxy failed");
                    close(SIPProxySock_);
                }
                msgProxy.clear();
            }
        }
    }
    
//    while((rtpApplicationPort_ == 0) || (rtpProxyPort_ == 0) || (rtpProxyHost_ == ""))
//        sleep(1);
    
//    RTPApplicationSock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
//    if (RTPApplicationSock_!=-1) {
//            connected_ = true;
//    }
//    RTPProxySock_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
//    if (RTPProxySock_!=-1) {
//            connected_ = true;
//    }
//    
//    // Listening for all incoming RTP packets    
//    siRTPListen_.sin_family = AF_INET;
//    siRTPListen_.sin_addr.s_addr = htonl(INADDR_ANY);
//    siRTPListen_.sin_port = htons(rtpExternalPort_);
//    
//    // Sending to RTP proxy
//    siRTPProxy_.sin_family = AF_INET;
//    siRTPProxy_.sin_port = htons(rtpProxyPort_);
//    siRTPProxy_.sin_addr = ((struct sockaddr_in *) addrInfoResult->ai_addr)->sin_addr;
//    if (inet_aton(rtpProxyHost_.c_str(), (struct in_addr *)&siRTPProxy_.sin_addr)==0) {
//          fprintf(stderr, "inet_aton() failed\n");
//          exit(1);
//    }
//    
//    if (bind(RTPSock_,(struct sockaddr *)&siRTPListen_, sizeof siRTPListen_) == -1) {
//        perror("binding datagram socket");
//        exit(1);
//    }
//
//    pthread_create( &listeningThread, NULL, &Connection::listenOnSockets, (void*) this);    
//    
//    pthread_join( listeningThread, NULL);

//       std::cout << msg << std::endl << std::endl;
//    int rtpPort;
//    if((rtpPort = pktSIP.getSDP()->getRTPPort())!=-1) {
//        pktSIP.getSDP()->setRTPPort(16100);
//    }
//
//    std::string rtpIP;
//    if((rtpIP = pktSIP.getSDP()->getRTPIP())!="") {
//        pktSIP.getSDP()->setRTPIP(LOCALHOST);
//        std::cout << pktSIP.getMsg() << std::endl << std::endl;
//    }

//       if((content = pktSIP.getField("REQUEST"))!=NULL) {
//           std::cout << ApplicationManager::getInstance().readLine(content) << std::endl;
//       }

//       if((content = pktSIP.getSDP()->getField("c"))!=NULL) {
//           std::cout << "c:   ";
//           ApplicationManager::getInstance().readLine(content);
//           std::cout << std::endl;
//       }         
//       if((content = pktSIP.getSDP()->getField("m"))!=NULL) {
//           std::cout << "m:   ";
//           ApplicationManager::getInstance().readLine(content);
//           std::cout << std::endl;
//       }     
}

Connection::Connection(const Connection& orig) {
}

Connection::~Connection() {
}

void Connection::write() {
//    if(!sout_.str().empty()) {
//        int dlen = sizeof(dest_);
//        sendto(sock_, sout_.str().c_str(), sout_.str().length(), 0, (struct sockaddr*)&dest_, dlen);
//        sout_.str("");
//    }
}

void Connection::read() {

}

void Connection::shutdown() {
#ifdef _WIN32
        closesocket(sock_);
        WSACleanup();
#else
//        close(sock_);
#endif
}

std::istringstream& Connection::getInputString() {
        return sin_;
}

std::ostringstream& Connection::getOutputStream() {
        return sout_;
}

int Connection::getPort() {
//        return port_;
}

void Connection::setPort(int port) {
//        port_ = port;
//        dest_.sin_port = htons(port_);
}

bool Connection::isClosed() {
        return false;
}

bool Connection::isConnected() {
        return connected_;
}

PacketHandler::Packet Connection::receivePacket() {
    
}

int Connection::sendPacket(PacketHandler::Packet packet) {
    
}
